﻿/*
Copyright 2010 GHI Electronics LLC
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 
*/

using System;
using System.IO.Ports;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.Hardware;

namespace GHIElectronics.NETMF.FEZ
{
    public static partial class FEZ_Components
    {
        public class SerialLCD : IDisposable
        {
            const byte DISP_ON = 0xC;    //Turn visible LCD on
            public const byte CLR_DISP = 0x01;      //Clear display
            const byte CUR_HOME = 2;      //Move cursor home and clear screen memory
            public const byte SET_CURSOR = 0x80;   //SET_CURSOR + X : Sets cursor position to X
            const byte Move_CURSOR_LEFT = 0x10;

            OutputCompare oc;
            const int MAX_TIMINGS_BUFFER_SIZE = 10;
            uint[] buffer = new uint[MAX_TIMINGS_BUFFER_SIZE];
            const int BAUD_RATE = 2400;
            const int BIT_TIME_US = 1 * 1000 * 1000 / BAUD_RATE;
            readonly int BYTE_TIME_MS;

            public void Dispose()
            {
                oc.Dispose();
                buffer = null;
            }

            public void SendByte(byte b)
            {
                SendByte(b, true);
            }
            public int SendByte(byte b, bool waitEnd)
            {
                bool currentPinState;
                int currentBufferIndex = 0;
                uint currentStateTiming;

                // start bit
                currentPinState = false;
                currentStateTiming = BIT_TIME_US;

                // data bits
                for (int i = 0; i < 8; i++)
                {
                    bool neededState = (b & (1 << i)) != 0;

                    if (neededState != currentPinState)
                    {
                        buffer[currentBufferIndex] = currentStateTiming;
                        currentStateTiming = BIT_TIME_US;
                        currentPinState = neededState;
                        currentBufferIndex++;
                    }
                    else
                    {
                        currentStateTiming += BIT_TIME_US;
                    }
                }

                // stop bit
                if (currentPinState != true)
                {
                    buffer[currentBufferIndex] = currentStateTiming;
                    currentBufferIndex++;
                }

                oc.Set(false, buffer, 0, currentBufferIndex, false);

                // wait till data is sent
                if(waitEnd)
                    Thread.Sleep(BYTE_TIME_MS);

                return BYTE_TIME_MS;
            }

            public void PutC(char c)
            {
                SendByte((byte)c);
            }

            private void SendCommand(byte cmd)
            {
                SendByte(0xFE);
                SendByte(cmd);
            }

            public void Print(string str)
            {
                for (int i = 0; i < str.Length; i++)
                    PutC(str[i]);
            }

            public void ClearScreen()
            {
                SendCommand(CLR_DISP);
            }

            public void CursorHome()
            {
                SendCommand(CUR_HOME);
            }

            public void SetCursor(byte row, byte col)
            {
                SendCommand((byte)(SET_CURSOR | row << 6 | col));
            }

            public void MoveLeft()
            {
                SendCommand(Move_CURSOR_LEFT);
            }

            public SerialLCD(FEZ_Pin.Digital pin)
            {
                BYTE_TIME_MS = (int)Math.Ceiling((double)BIT_TIME_US * MAX_TIMINGS_BUFFER_SIZE / 1000);
                oc = new OutputCompare((Cpu.Pin)pin, true, MAX_TIMINGS_BUFFER_SIZE);
                // Initilaize LCD

                SendCommand(DISP_ON);
                SendCommand(CLR_DISP);
            }
        }
    }
}